=begin
 *
 * File: sif_parser.rb
 * Scanner and Parser code for the S-expression Interpreter Framework (SIF).
 * Version: 1.0
 * Copyright (C) 2006-2008 by Ariel Ortiz <ariel.ortiz@itesm.mx>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
=end

#--------------------------------------------------------------------
class InterpreterError < RuntimeError; end

#--------------------------------------------------------------------
class Parser

  def initialize(str)
    @tokens = tokenize(str)
    next_token
  end

  def each
    while not @current_token.nil?
      yield read_datum
    end
  end

private

  def tokenize(s)

    tokenRegexp = /
        (\s)                               # group 1: Spaces
      | (;.*)                              # group 2: Comments
      | (-?\d+)                            # group 3: Integer numbers
      | ( (?:\w|[!$%&*+\-.\/:<=>?^_|~])+ ) # group 4: Symbols
      | ([()])                             # group 5: Parenthesis
      | (.)                                # group 6: Anything else
    /x

    tokens = []
    loop do
      m = tokenRegexp.match(s)
      break unless m
      s = m.post_match
      next if m[1] or m[2]
      raise InterpreterError.new("Illegal character: '#{m[6]}'") if m[6]
      tokens << m[3].to_i if m[3]
      tokens << m[4].to_sym if m[4]
      tokens << m[5] if m[5]
    end

    return tokens
  end

  def next_token
    if @tokens.empty?
      @current_token = nil
    else
      @current_token = @tokens.delete_at(0)
    end
  end

  def read_list
    next_token
    r = []
    while @current_token != ')'
      r << read_datum
    end
    next_token
    return r
  end

  def read_atom
    r = @current_token
    next_token
    return r
  end

  def read_datum
    if @current_token.nil?
      raise InterpreterError.new('Found end of input too soon')
    elsif @current_token == ')'
      raise InterpreterError.new("Found too many ')'")
    elsif @current_token == '('
      return read_list
    else
      return read_atom()
    end
  end

end
